package cas

import (
	"net/http"
	"path"
	"slices"
	"strings"

	ge "gitee.com/haifengat/gin-ex/v2"
	"github.com/casdoor/casdoor-go-sdk/casdoorsdk"
	"github.com/gin-gonic/gin"
	"github.com/pkg/errors"
)

// 定义全局结构体
type SigninRequest struct {
	UserName string `json:"username"`
	Password string `json:"password"`
}

type ChangePWDRequest struct {
	OldPassword string `json:"oldPassword"`
	NewPassword string `json:"newPassword"`
}

// RouteAuth 设置与认证相关的路由
// 先初始化 cas
//
//	cas.InitCasdoor(endPoint, clientID, clientSecret, token, orgName, appName)
//
// WhiteList = append(WhiteList, "192.168.52.55", "172.29.16.1", "172.28.64.1")
// @param g Gin 的路由组
func RouteAuth(g *gin.RouterGroup) {
	// 使用中间件检查用户是否已登录
	g.Use(checkAuthHandler)

	// 添加 /code 获取 code
	g.POST("/code", ge.CreateQryHandler(func(ctx *gin.Context) (data any, total int, errRtn error) {
		var req SigninRequest

		// 绑定请求体并检查错误
		if err := ctx.ShouldBind(&req); err != nil {
			errRtn = errors.Wrap(err, "绑定 JSON")
			return
		}

		// 输入验证
		if req.UserName == "" || req.Password == "" {
			errRtn = errors.New("用户名或密码不能为空")
			return
		}
		data, errRtn = getCode(req.UserName, req.Password)
		return
	}))

	// 添加 /signin 路径，用于用户登录
	g.POST("/signin", ge.CreateQryHandler(func(ctx *gin.Context) (data any, total int, errRtn error) {
		data, errRtn = signinHandler(ctx)
		return
	}))

	g.POST("/login", ge.CreateQryHandler(loginHandler))

	// 添加 /currentUser 路径，用于获取当前登录用户的信息
	g.GET("/currentUser", ge.CreateQryHandler(func(ctx *gin.Context) (data any, total int, errRtn error) {
		claims, err := GetClaims(ctx)
		if err != nil {
			errRtn = errors.Wrap(err, "获取用户信息失败")
			return
		}
		if claims == nil {
			errRtn = errors.New("未登录")
		} else {
			data = *claims
		}
		return
	}))

	// 添加 /signout 路径，用于用户登出
	g.POST("/signout", ge.CreateUpHandler(signoutHandler))

	g.POST("/check", ge.CreateUpHandler(func(ctx *gin.Context) (errRtn error) {
		claims, err := GetClaims(ctx)
		if err != nil {
			errRtn = errors.Wrap(err, "获取用户信息失败")
			return
		}
		if claims == nil {
			errRtn = errors.New("未登录")
		}
		return
	}))

	// 添加 /changePWD 路径，用于修改用户密码
	g.POST("/changePWD", ge.CreateUpHandler(changePWDHandler))
}

// loginHandler 返回一个处理用户登录请求的函数
// @return 处理登录请求的函数
func loginHandler(ctx *gin.Context) (data any, total int, errRtn error) {
	var req SigninRequest

	// 绑定请求体并检查错误
	if err := ctx.BindJSON(&req); err != nil {
		errRtn = errors.Wrap(err, "绑定 JSON")
		return
	}

	// 输入验证
	if req.UserName == "" || req.Password == "" {
		errRtn = errors.New("用户名或密码不能为空")
		return
	}

	// 调用签入函数并检查错误
	data, errRtn = Signin(req.UserName, req.Password)
	return
}

// signoutHandler 返回一个处理用户登出请求的函数
// @return 处理登出请求的函数
func signoutHandler(ctx *gin.Context) (errRtn error) {
	errRtn = Signout(ctx)
	return
}

// changePWDHandler 返回一个处理用户密码修改请求的函数
// @return 处理密码修改请求的函数
func changePWDHandler(ctx *gin.Context) (errRtn error) {
	var req ChangePWDRequest

	// 绑定请求体并检查错误
	if err := ctx.BindJSON(&req); err != nil {
		errRtn = errors.Wrap(err, "绑定 JSON")
		return
	}

	// 获取用户信息
	claims, err := GetClaims(ctx)
	if err != nil {
		errRtn = errors.Wrap(err, "获取用户信息失败")
		return
	}
	if claims == nil {
		errRtn = errors.New("未登录")
		return
	}

	// 调用更改密码函数并检查错误
	errRtn = ChangePwd(claims.User.Name, req.OldPassword, req.NewPassword)
	return
}

// checkAuthHandler 是一个中间件函数，用于检查和验证用户的认证信息
//
// 它跳过了对本地测试环境、signin、signout、白名单IP的认证
// 对于已认证的用户，它将用户信息设置到上下文中，以便后续处理
func checkAuthHandler(ctx *gin.Context) {
	// url 最后的路径
	baseURI := path.Base(ctx.Request.URL.Path)
	// 跳过对登录请求,退出请示的认证, 跳过白名单的 IP
	if strings.Contains(ctx.Request.Host, "localhost") || slices.Contains([]string{"code", "login", "signin", "signout"}, baseURI) || slices.Contains(WhiteList, ctx.GetHeader("X-Real-IP")) || slices.Contains(WhiteList, ctx.ClientIP()) { // 白名单
		ctx.Next()
		return
	}

	// 登录信息验证
	claims, err := GetClaims(ctx)
	if err != nil {
		ctx.JSON(http.StatusOK, gin.H{
			"Success": false,
			"Data":    "认证失败" + err.Error(),
		})
		// 不调用该请求的剩余处理程序
		ctx.Abort()
		return
	}
	if claims == nil {
		ctx.JSON(http.StatusOK, gin.H{
			"Success": false,
			"Data":    "认证失败",
		})
		// 不调用该请求的剩余处理程序
		ctx.Abort()
		return
	}
	ctx.Set("user", claims.User) // 保存登录用户信息, 供后续调用
	ctx.Next()
}

// signinHandler 通过 code 获取 token
func signinHandler(ctx *gin.Context) (data any, errRtn error) {
	code := ctx.Query("code")
	state, _ := generateRandomState()

	data, errRtn = casdoorsdk.GetOAuthToken(code, state)
	return
}
